package cn.org.rookie.mybatismapper;

import cn.org.rookie.mybatismapper.annotation.Transient;
import cn.org.rookie.mybatismapper.context.MapperContext;
import cn.org.rookie.mybatismapper.entiey.Column;
import cn.org.rookie.mybatismapper.entiey.PrimaryKey;
import cn.org.rookie.mybatismapper.entiey.Table;
import cn.org.rookie.mybatismapper.sql.SqlBuilder;
import cn.org.rookie.mybatismapper.sql.Wrapper;
import cn.org.rookie.mybatismapper.utils.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.builder.annotation.ProviderContext;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BaseMapperProvider {

    private static Class<?> getEntityType(ProviderContext context) {
        return (Class<?>) ((ParameterizedType) (context.getMapperType().getGenericInterfaces()[0])).getActualTypeArguments()[0];
    }

    private Table transform(ProviderContext context) {
        Class<?> type = getEntityType(context);

        Table table = MapperContext.get(type.getName());
        if (table == null) {
            table = new Table();
            cn.org.rookie.mybatismapper.annotation.Table tableAnno = type.getAnnotation(cn.org.rookie.mybatismapper.annotation.Table.class);

            if (tableAnno != null) {
                table.setName("".equals(tableAnno.value()) ? type.getSimpleName() : tableAnno.value());
            } else {
                table.setName(StringUtils.humpToUnderline(type.getSimpleName()));
            }

            List<Column> columns = new ArrayList<>();
            table.setColumns(columns);
            List<Field> fields = new ArrayList<>();
            Class<?> superclass = type.getSuperclass();
            fields.addAll(Arrays.asList(superclass.getDeclaredFields()));
            fields.addAll(!fields.isEmpty() ? 0 : 1, Arrays.asList(type.getDeclaredFields()));


            for (Field field : fields) {
                field.setAccessible(true);
                Transient transientAnno = field.getAnnotation(Transient.class);
                if (transientAnno == null) {
                    Column column = new Column();
                    column.setFieldName(field.getName());
                    cn.org.rookie.mybatismapper.annotation.Column columnAnno = field.getAnnotation(cn.org.rookie.mybatismapper.annotation.Column.class);
                    if (columnAnno != null) {
                        column.setName("".equals(columnAnno.value()) ? field.getName() : columnAnno.value());
                        column.setComment(columnAnno.comment());
                    } else {
                        column.setName(StringUtils.humpToUnderline(field.getName()));
                    }
                    cn.org.rookie.mybatismapper.annotation.PrimaryKey primaryAnno = field.getAnnotation(cn.org.rookie.mybatismapper.annotation.PrimaryKey.class);
                    if (primaryAnno != null) {
                        table.setPrimaryKey(new PrimaryKey(column.getName(), column.getFieldName()));
                    }
                    columns.add(column);
                }
            }
        }

        return table;
    }

    public String find(ProviderContext context) {
        return new SqlBuilder(transform(context)).select().byPrimaryKey().build();
    }

    public String findByWrapper(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return new SqlBuilder(transform(context)).select().where((Wrapper) paramMap.get("wrapper")).build();
    }

    public String findAll(ProviderContext context) {
        return new SqlBuilder(transform(context)).select().build();
    }

    public String update(ProviderContext context) {
        return new SqlBuilder(transform(context)).update().byPrimaryKey().build();
    }

    public String updateByWrapper(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return new SqlBuilder(transform(context)).update().where((Wrapper) paramMap.get("wrapper")).build();
    }

    public String insert(ProviderContext context) {
        return new SqlBuilder(transform(context)).insert().build();
    }

    public String delete(ProviderContext context) {
        return new SqlBuilder(transform(context)).delete().byPrimaryKey().build();
    }

    public String deleteByWrapper(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return new SqlBuilder(transform(context)).delete().where((Wrapper) paramMap.get("wrapper")).build();
    }
}
